﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.ServiceModel.Description;
using Microsoft.Crm.Sdk.Messages;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Client;
using Microsoft.Xrm.Sdk.Query;

namespace Crm.Utilities.SolutionsDeploymentTool
{
    class Program
    {
        private static string MESSAGE_PREFIX 
        {
            get { return string.Format("Execution Message | {0} |", DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss")); } 
        }
        private static IOrganizationService ServiceProxy_Origin = null;
        private static IOrganizationService ServiceProxy_Target = null;

        static void Main(string[] args)
        {
            try
            {
                InitializeServiceProxies();
                Console.Clear();

                while (true)
                {
                    Console.WriteLine("===============================================");
                    Console.WriteLine("    Commands ");
                    Console.WriteLine("===============================================");
                    Console.WriteLine("0 - Exit Application");
                    Console.WriteLine("1 - Export Solution(s)");
                    Console.WriteLine("2 - Export Managed Solution(s)");
                    Console.WriteLine("3 - Import Solution(s)");
                    Console.WriteLine("4 - Import Solution(s) & Publish");
                    Console.WriteLine("5 - Export & Import Solution(s)");
                    Console.WriteLine("6 - Export & Import Solution(s) & Publish");
                    Console.WriteLine("7 - Publish all customizations (destination sever only)");
                    Console.WriteLine("===============================================");
                    Console.WriteLine();

                    Console.WriteLine("Command to run : ");
                    string x = Console.ReadLine();

                    x = x.Trim().ToLower();
                    if (x.Length == 1)
                    {
                        char cmd = x[0];
                        Console.WriteLine();

                        switch (cmd)
                        {
                            case '0':
                                Environment.Exit(0);
                                break;
                            case '1':
                                ExportCRMSolutions();
                                break;
                            case '2':
                                ExportCRMSolutions(true);
                                break;
                            case '3':
                                ImportCRMSolutions();
                                break;
                            case '4':
                                ImportCRMSolutions(true);
                                break;
                            case '5':
                                ExportCRMSolutions();
                                ImportCRMSolutions();
                                break;
                            case '6':
                                ExportCRMSolutions();
                                ImportCRMSolutions(true);
                                break;
                            case '7':
                                Console.WriteLine();
                                PublishAllCustomizations();
                                break;
                            default:
                                Console.WriteLine("Unsupported command ({0}).", cmd.ToString());
                                break;
                        }
                    }
                    else
                    {
                        Console.WriteLine("Please enter only the number for command to execute.");
                    }

                    Console.WriteLine();
                }
            }
            catch (Exception e)
            {
                Console.WriteLine();
                Console.WriteLine("************ Application Execution Error ************");
                Console.WriteLine(string.Format("**** Exception Message    : {0}", e.Message));
                Console.WriteLine(string.Format("**** Exception Source     : {0}", e.Source));
                Console.WriteLine(string.Format("**** Exception StackTrace : {0}", e.StackTrace));

                Console.WriteLine();
                Console.WriteLine();
                Console.WriteLine("Press any key to exit...");
                Console.ReadKey(true);
            }
        }

        #region Application Logic Methods

        /// <summary>
        /// Initialize the CRM Services & Set timeouts
        /// </summary>
        private static void InitializeServiceProxies()
        {
            Console.WriteLine(string.Format("Setting up CRM connections to {0} and {1}, please wait...", ConfigurationHelper.CrmSourceOrganization, ConfigurationHelper.CrmTargetOrganization));

            string originUrl = string.Format("{0}/{1}/XRMServices/2011/Organization.svc", ConfigurationHelper.CrmSourceServer, ConfigurationHelper.CrmSourceOrganization);
            string targetUrl = string.Format("{0}/{1}/XRMServices/2011/Organization.svc", ConfigurationHelper.CrmTargetServer, ConfigurationHelper.CrmTargetOrganization);

            ClientCredentials credentials = new ClientCredentials();

            if (!string.IsNullOrEmpty(ConfigurationHelper.CrmDomain) && !string.IsNullOrEmpty(ConfigurationHelper.CrmUsername) && !string.IsNullOrEmpty(ConfigurationHelper.CrmPassword))
            {
                credentials.Windows.ClientCredential = new System.Net.NetworkCredential(ConfigurationHelper.CrmUsername, ConfigurationHelper.CrmPassword, ConfigurationHelper.CrmDomain);
            }
            else
            {
                credentials.Windows.ClientCredential = CredentialCache.DefaultNetworkCredentials;
            }

            var config1 = ServiceConfigurationFactory.CreateConfiguration<IOrganizationService>(new Uri(originUrl));
            var config2 = ServiceConfigurationFactory.CreateConfiguration<IOrganizationService>(new Uri(targetUrl));

            ServiceProxy_Origin = (new OrganizationServiceProxy(config1, credentials)) as IOrganizationService;
            ServiceProxy_Target = (new OrganizationServiceProxy(config2, credentials)) as IOrganizationService;

            ((OrganizationServiceProxy)ServiceProxy_Origin).Timeout = ConfigurationHelper.ServiceTimout;
            ((OrganizationServiceProxy)ServiceProxy_Target).Timeout = ConfigurationHelper.ServiceTimout;

            Console.WriteLine("CRM Connections set up : Success");
        }

        /// <summary>
        /// Retrieves the CRM solution(s) based on name provided in configuration and
        /// exports the solution file in the specified folder. File name is in the same format
        /// as CRM's out of the box solutions (i.e. MySolution_1_0_1_0.zip or MySolution_1_0_1_0_managed.zip)
        /// </summary>
        /// <param name="managed">Export Solution as managed if TRUE</param>
        private static void ExportCRMSolutions(bool managed = false)
        {

            if (ConfigurationHelper.CrmSolutions.Count() > 0)
            {
                Console.WriteLine(string.Format("{0} Exporting {1} Solution(s)", MESSAGE_PREFIX, ConfigurationHelper.CrmSolutions.Count().ToString()));

                foreach (string solution in ConfigurationHelper.CrmSolutions)
                {
                    // Retrieve version number for file name
                    QueryExpression querySolution = new QueryExpression
                    {
                        EntityName = "solution",
                        ColumnSet = new ColumnSet(new string[] { "version", "versionnumber" }),
                        Criteria = new FilterExpression()
                    };

                    querySolution.Criteria.AddCondition("uniquename", ConditionOperator.Equal, solution);
                    EntityCollection matchingSolutions = ServiceProxy_Origin.RetrieveMultiple(querySolution);

                    string version = matchingSolutions.Entities[0].Attributes["version"].ToString();
                    version = version.Replace('.', '_');

                    // Set Exported solution file name
                    string filename;
                    if (managed)
                    {
                        filename = solution + "_" + version + "_managed.zip";
                    }
                    else
                    {
                        filename = solution + "_" + version + ".zip";
                    }

                    if (managed)
                        Console.WriteLine(string.Format("{0} Exporting Managed Solution '{1}' from CRM Organization {2}", MESSAGE_PREFIX, solution, ConfigurationHelper.CrmSourceOrganization));
                    else
                        Console.WriteLine(string.Format("{0} Exporting Unmanaged Solution '{1}' from CRM Organization {2}", MESSAGE_PREFIX, solution, ConfigurationHelper.CrmSourceOrganization));

                    // Export Solution Request
                    ExportSolutionRequest exportRequest = new ExportSolutionRequest
                    {
                        Managed = managed,
                        SolutionName = solution
                    };
                    ExportSolutionResponse exportResponse = ServiceProxy_Origin.Execute(exportRequest) as ExportSolutionResponse;

                    // Write to disk
                    File.WriteAllBytes(ConfigurationHelper.CrmSolutionsDirectory + filename, exportResponse.ExportSolutionFile);

                    Console.WriteLine(string.Format("{0} Solution '{1}' Saved to disk as '{2}'", MESSAGE_PREFIX, solution, filename));
                }
            }
        }

        /// <summary>
        /// Import CRM solutions in target organization
        /// Note : uses the most recently modified UMANAGED solution in the target folder 
        /// (no solution number comparison and no import of managed solutions either)
        /// </summary>
        /// <param name="publishAfterImport"></param>
        private static void ImportCRMSolutions(bool publishAfterImport = false)
        {
            if (ConfigurationHelper.CrmSolutions.Count() > 0)
            {
                Console.WriteLine(string.Format("{0} Importing {1} Solution(s)", MESSAGE_PREFIX, ConfigurationHelper.CrmSolutions.Count().ToString()));

                List<string> solutionFilesToImport = new List<string>();

                foreach (string solution in ConfigurationHelper.CrmSolutions)
                {
                    // get list of filenames matching our solution name
                    string[] dirs = Directory.GetFiles(ConfigurationHelper.CrmSolutionsDirectory, solution + "_*.zip");
                    SortedSet<FilenameDate> possibleCandidates = new SortedSet<FilenameDate>(new byDate());
                    foreach (string f in dirs)
                    { // retrieve their modified dates and sort by these
                        FileInfo d = new FileInfo(f);
                        possibleCandidates.Add(new FilenameDate { filename = f, modifiedDate = d.LastWriteTime });
                    }
                    IEnumerator<FilenameDate> candidates = possibleCandidates.Reverse().GetEnumerator();
                    while (candidates.MoveNext() != false)
                    {   // take the newest file, excluding managed solutions
                        if (!candidates.Current.filename.Contains("managed.zip"))
                        {
                            solutionFilesToImport.Add(candidates.Current.filename);
                            break;
                        }
                    }
                }

                foreach (string file in solutionFilesToImport)
                {
                    Console.WriteLine(string.Format("{0} Importing Solution at '{1}' to CRM Organization {2}", MESSAGE_PREFIX, file, ConfigurationHelper.CrmTargetOrganization));

                    ImportSolutionRequest importSolution = new ImportSolutionRequest
                    {
                        OverwriteUnmanagedCustomizations = true,
                        CustomizationFile = File.ReadAllBytes(file),
                        PublishWorkflows = true,
                        ImportJobId = Guid.NewGuid()
                    };

                    try
                    {
                        ImportSolutionResponse importResponse = ServiceProxy_Target.Execute(importSolution) as ImportSolutionResponse;
                    }
                    catch (Exception e)
                    {
                        if (!e.Message.StartsWith("The request channel timed out while waiting for a reply after "))
                        {
                            throw e; // if timed out, ignore exception, otherwise throw
                        }
                    }

                    String SolutionImportResult = null;
                    String PercentDone = null;
                    double percent = 0.0;
                    do
                    {

                        try
                        {
                            Entity job = ServiceProxy_Target.Retrieve("importjob", importSolution.ImportJobId, new
                            ColumnSet(new System.String[] { "data", "solutionname" }));

                            System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
                            doc.LoadXml(job["data"].ToString());
                            PercentDone = doc.SelectSingleNode("importexportxml/@progress").Value;
                            SolutionImportResult = doc.SelectSingleNode("//solutionManifest/result/@result").Value;
                            percent = Double.Parse(PercentDone);
                        }
                        catch (Exception e)
                        {
                            Console.WriteLine(e.Message);
                        }

                        if (percent < 100)
                        {
                            Console.WriteLine("    Import is {0}% complete.", percent);
                            System.Threading.Thread.Sleep(10000);
                        }

                    } while (percent < 100 && SolutionImportResult.CompareTo("success") == 0);

                    Console.WriteLine("{0} Solution Import Result: {1}", MESSAGE_PREFIX, SolutionImportResult.ToUpper());
                }

                if (publishAfterImport)
                    PublishAllCustomizations();
            }
        }

        /// <summary>
        /// Publish all customizations in Target environment
        /// </summary>
        /// <returns></returns>
        private static PublishAllXmlResponse PublishAllCustomizations()
        {
            PublishAllXmlResponse response = null;
            PublishAllXmlRequest req = new PublishAllXmlRequest();
            req.RequestId = Guid.NewGuid();

            Console.WriteLine(string.Format("{0} Publishing all customizations for organization {1}. Please wait...", MESSAGE_PREFIX, ConfigurationHelper.CrmTargetOrganization));
            try
            {
                response = ServiceProxy_Target.Execute(req) as PublishAllXmlResponse;
            }
            catch (Exception e)
            {
                if (!e.Message.StartsWith("The request channel timed out while waiting for a reply after "))
                {
                    throw e; // if timed out, ignore exception
                }
                else
                {
                    Console.WriteLine("Publishing all customizations has not yet completed (the service request timed out).");
                }
            }

            Console.WriteLine(string.Format("{0} Publishing all customizations *** Success ***", MESSAGE_PREFIX));

            return response;
        }

        #endregion
    }

    
}
